package context

import (
	"context"
	"fmt"
	"runtime"
	"strings"
	"time"
)

/**
Context 使用原则
不要把 Context 放在结构体中，要以参数的方式传递
以 Context 作为参数的函数方法，应该把 Context 作为第一个参数，放在第一位。
给一个函数方法传递 Context 的时候，不要传递 nil，如果不知道传递什么，就使用 context.TODO
Context 的 Value 相关方法应该传递必须的数据，不要什么数据都使用这个传递
Context 是线程安全的，可以放心的在多个 goroutine 中传递
*/

/**
Done 方法返回一个只读的 chan，类型为 struct{}，我们在 goroutine 中，如果该方法返回的 chan 可以读取，则意味着 parent context 已经发起了取消请求，我们通过 Done 方法收到这个信号后，就应该做清理操作，然后退出 goroutine，释放资源。

Err 方法返回取消的错误原因，因为什么 Context 被取消。

Value 方法获取该 Context 上绑定的值，是一个键值对，所以要通过一个 Key 才可以获取对应的值，这个值一般是线程安全的。

以上四个方法中常用的就是 Done 了，如果 Context 取消的时候，我们就可以得到一个关闭的 chan，关闭的 chan 是可以读取的，所以只要可以读取的时候，就意味着收到 Context 取消的信号了，以下是这个方法的经典用法。
*/

func withCancel() {
	//无论是Goroutine，他们的创建和调用关系总是像层层调用进行的，就像人的辈分一样，
	//而更靠顶部的Goroutine应有办法主动关闭其下属的Goroutine的执行（不然程序可能就失控了）。
	//为了实现这种关系，Context结构也应该像一棵树，叶子节点须总是由根节点衍生出来的。
	//
	//要创建Context树，第一步就是要得到根节点，context.Background函数的返回值就是根节点：
	// WithCancel函数，它是将父节点复制到子节点，并且还返回一个额外的CancelFunc函数类型变量
	//调用CancelFunc对象将撤销对应的Context对象，这就是主动撤销Context的方法。在父节点的Context所对应的环境中，
	//通过WithCancel函数不仅可创建子节点的Context，同时也获得了该节点Context的控制权，一旦执行该函数，则该节点Context就结束
	ctx, cancle := context.WithCancel(context.Background())

	go func() {
		for {
			select {
			// 执行了cancel方法，Done返回的是空struct
			case v := <-ctx.Done():
				fmt.Println("get the stop channel", v)
				runtime.Goexit()
				return
			default:
				time.Sleep(time.Duration(1) * time.Second)
				fmt.Println("still working")

			}

			//fmt.Println("----------------------run------------------------------")

		}

	}()

	time.Sleep(time.Duration(5) * time.Second)
	cancle()
	fmt.Println("cancle----")

	for {
		time.Sleep(time.Duration(30) * time.Second)
		fmt.Println("sleep 30s")
	}

}

func withDeadline() {
	//WithDeadline 函数，和 WithCancel 差不多，它会多传递一个截止时间参数，意味着到了这个时间点，会自动取消 Context，
	//当然我们也可以不等到这个时候，可以提前通过取消函数进行取消。

	ctx, cancle := context.WithDeadline(context.Background(), time.Now().Add(time.Duration(10)*time.Second))

	go func() {
		select {
		case v := <-ctx.Done():
			fmt.Println("get the stop channel", v)
			return
		default:
			fmt.Println("still working")

		}
	}()

	time.Sleep(time.Duration(15) * time.Second)

	//cancel绑定了一定的资源，不能不调用
	cancle()

}

func withTimeout() {
	//WithTimeout函数与WithDeadline类似，只不过它传入的是从现在开始Context剩余的生命时长。
	//他们都同样也都返回了所创建的子Context的控制权，一个CancelFunc类型的函数变量。

	ctx, cancle := context.WithTimeout(context.Background(), time.Duration(5)*time.Second)
	defer cancle()

	go func(ctx context.Context) {

		for {
			select {
			//// 执行了cancel方法或超时，Done返回的是struct{};ctx.Err()非nil
			case v := <-ctx.Done():

				fmt.Println("complete  ", v, ctx.Err(), nil == ctx.Err())
				deadline,ok :=ctx.Deadline()
				fmt.Println(deadline,ok)
				fmt.Println(deadline.After(time.Now()))

				msg := ctx.Err().Error()

				// 下面是判断超时的方式
				//context canceled
				if strings.Contains(msg, "canceled") {
					fmt.Println("cancel called")
				}

				// context deadline exceeded
				if strings.Contains(msg, "deadline") {
					fmt.Println("run timeout")
				}



				// 截止时间在当前时间之后则是cancle调用,否则是超时
				if deadline.After(time.Now()) {
					fmt.Println("cancel called")
				} else{
					fmt.Println("run timeout")
				}

				runtime.Goexit()
			default:
				fmt.Println("still working")
				time.Sleep(time.Duration(1) * time.Second)
			}
		}

	}(ctx)

	//cancle()
	fmt.Println("----------------------------")

	time.Sleep(time.Duration(40) * time.Second)

}

var key string = "name"

func withValue() {
	//WithValue函数，它返回parent的一个副本，调用该副本的Value(key)方法将得到val。这样我们不光将根节点原有的值保留了，
	//还在子孙节点中加入了新的值，注意若存在Key相同，则会被覆盖。

	/**
	通过传递参数的方式，可以把 name 的值传递给监控函数。在这个例子里，我们实现一样的效果，但是通过的是 Context 的 Value 的方式。

	我们可以使用 context.WithValue 方法附加一对 K-V 的键值对，这里 Key 必须是等价性的，也就是具有可比性；Value 值要是线程安全的。

	这样我们就生成了一个新的 Context，这个新的 Context 带有这个键值对，在使用的时候，可以通过 Value 方法读取 ctx.Value(key)。

	记住，使用 WithValue 传值，一般是必须的值，不要什么值都传递。
	*/

	ctx, cancel := context.WithCancel(context.Background())
	//附加值
	valueCtx := context.WithValue(ctx, key, "【监控1】")
	go watch(valueCtx)
	time.Sleep(10 * time.Second)
	fmt.Println("可以了，通知监控停止")
	//cancel绑定了一定的资源，不能不调用
	cancel()
	//为了检测监控过是否停止，如果没有监控输出，就表示停止了
	time.Sleep(5 * time.Second)

}

func watch(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			//取出值
			fmt.Println(ctx.Value(key), "监控退出，停止了...")
			return
		default:
			//取出值
			fmt.Println(ctx.Value(key), "goroutine监控中...")
			time.Sleep(2 * time.Second)
		}
	}
}
